﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Paypayes.Aelda.Core
{
    public class EventPackerGenerator
    {
        public static IEventPacker Generate()
        {
            string ns = "Paypayes.Aelda.Core.SimulationEvents";

            StringBuilder[] serUnserCodes = new StringBuilder[2];

            serUnserCodes[0] = new StringBuilder(@"        public void Pack(SimulationEvent sevt, ref Object buffer)
        {
            NetOutgoingMessage msg = (NetOutgoingMessage)buffer;
            msg.Write((Byte)sevt.EventType);
            msg.Write((Byte)sevt.Channel);
            msg.Write(sevt.SourceId);
            msg.Write(sevt.TargetId);

            switch (sevt.EventType)
            {

");
            serUnserCodes[1] = new StringBuilder(@"        public SimulationEvent Unpack(Object buffer)
        {
            NetBuffer msg = (NetBuffer)buffer;
            EventType type = (EventType)msg.ReadByte();
            EventChannel channel = (EventChannel)msg.ReadByte();
            UInt64 sourceId = msg.ReadUInt64();
            UInt64 targetId = msg.ReadUInt64();
            SimulationEvent evt = null;

            switch (type)
            {
");

            System.Text.StringBuilder source = new System.Text.StringBuilder(@"using System;
using Lidgren.Network;
using Paypayes.Aelda.Core.SimulationEvents;
using Paypayes.Aelda.Core;

namespace Paypayes.Aelda.Helper
{
    public class EventPacker : IEventPacker
    {
%0

%1
}}
");

            Type aa = Type.GetType("Paypayes.Aelda.Core.SimulationEvents.Login, Paypayes.Aelda.Core");

            foreach (String e in Enum.GetNames(typeof(Paypayes.Aelda.Core.EventType)))
            {
                String typeName = String.Format("{0}.{1}, Paypayes.Aelda.Core", ns, e);
                System.Diagnostics.Debug.Write(String.Format("Looking for type: {0}...", typeName));

                Type t = Type.GetType(typeName);

                if (t != null)
                {
                    System.Diagnostics.Trace.WriteLine("found");

                    serUnserCodes[0].Append(String.Format("case EventType.{0}:\n", e));
                    serUnserCodes[1].Append(String.Format("case EventType.{0}:\n", e));
                    serUnserCodes[1].Append(String.Format("evt = new {0}() {{", e));

                    foreach (PropertyInfo property in t.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.DeclaredOnly))
                    {
                        System.Diagnostics.Trace.WriteLine("  - " + property.PropertyType.Name);
                        System.Diagnostics.Trace.Write("  - " + property.Name + ", attr: ");

                        PackAttribute pa = GetPackAttribute(property);
                        if (pa != null)
                        {
                            String packAs = (pa.PackAs == null) ? property.PropertyType.Name : pa.PackAs;
                            String unpackCastAs = (pa.UnpackCastAs == null) ? "" : String.Format("({0})", pa.UnpackCastAs);

                            serUnserCodes[0].Append(String.Format("msg.Write(Convert.To{0}((({1})sevt).{2}));\n", packAs, t.Name, property.Name));
                            serUnserCodes[1].Append(String.Format("{0} = {1}msg.Read{2}(),", property.Name, unpackCastAs, packAs));

                            System.Diagnostics.Trace.WriteLine(String.Format("PackAs={0}, UnpackAs={1}", packAs, unpackCastAs));
                        }
                        System.Diagnostics.Trace.Write("\n");
                    }
                    if (serUnserCodes[1][serUnserCodes[1].Length - 1] == ',')
                    {
                        serUnserCodes[1].Remove(serUnserCodes[1].Length - 1, 1);
                    }
                    serUnserCodes[1].Append("};\n");

                    serUnserCodes[0].Append("break;\n");
                    serUnserCodes[1].Append("break;\n");
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine("not found");
                }
            }
            serUnserCodes[0].Append("}}");
            serUnserCodes[1].Append("}return evt;}");

            source.Replace("%0", serUnserCodes[0].ToString());
            source.Replace("%1", serUnserCodes[1].ToString());

            //var cp2 = new System.CodeDom.Compiler.CompilerParameters();
            var cp = new System.CodeDom.Compiler.CompilerParameters
            {
                GenerateInMemory = false,    // you will get a System.Reflection.Assembly back
                GenerateExecutable = false, // Dll
                
                IncludeDebugInformation = false,
                CompilerOptions = "",
                OutputAssembly = "Paypayes.Core.EventPacker.dll"
            };
            cp.ReferencedAssemblies.Add("System.dll");
            cp.ReferencedAssemblies.Add("Lidgren.Network.dll");
            cp.ReferencedAssemblies.Add("Paypayes.Aelda.Core.dll");
            var csharp = new Microsoft.CSharp.CSharpCodeProvider();

            // this actually runs csc.exe:
            System.CodeDom.Compiler.CompilerResults cr =
                  csharp.CompileAssemblyFromSource(cp, source.ToString());

            if (cr.Errors.Count > 0)
            {
                foreach (System.CodeDom.Compiler.CompilerError error in cr.Errors)
                {
                    System.Diagnostics.Trace.WriteLine("Line number " + error.Line
                        + ", Error Number: " + error.ErrorNumber
                        + ", '" + error.ErrorText + ";");
                }
            }

            Object eventPacker = cr.CompiledAssembly.CreateInstance("Paypayes.Aelda.Helper.EventPacker");
            return (IEventPacker)eventPacker;
        }

        private static PackAttribute GetPackAttribute(PropertyInfo pi)
        {
            foreach (object attribute in pi.GetCustomAttributes(true))
            {
                if (attribute is PackAttribute)
                {
                    return attribute as PackAttribute;
                }
            }

            return null;
        }
    }
}
